package com.tuniu.agents.common.logger;

import com.tuniu.agents.advisor.ChatEnvironmentMemory;
import com.tuniu.agents.common.AgentsConstants;
import com.tuniu.agents.tool.EnvParam;
import com.tuniu.agents.tool.EnvRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Aspect for global variable management
 */
@Aspect
@Component
@Order(0)
@Slf4j
public class ToolEnvAspect {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ChatEnvironmentMemory chatEnvironmentMemory;

    private static final Logger logger = LoggerFactory.getLogger(ToolEnvAspect.class);

    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("execution(* com.tuniu.agents.tools.train.tool.*.apply(..))")
    public Object envRequestExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        // Get method signature and method object
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // Get conversation ID
        Object[] args = joinPoint.getArgs();
        String conversationId = getConversationId(args);

        for (int i = 0; i < args.length; i++) {
            // Get @EnvRequest annotation on parameters
            EnvRequest envRequestAnnotation = method.getParameters()[i].getAnnotation(EnvRequest.class);
            if (envRequestAnnotation != null) {
                Object requestObject = args[i];
                // Extract fields with @EnvParam annotation from requestObject
                if (requestObject != null) {
                    if (requestObject.getClass().isRecord()) {
                        // If it's a record type, try to create a new instance and replace the parameter
                        Object newRecord = processRecordObject(requestObject, conversationId);
                        if (newRecord != null) {
                            // Replace the object in the parameter array
                            args[i] = newRecord;
                            log.info("Record parameter replacement successful: new object = {}", newRecord);
                        }
                    } else {
                        // Process fields of regular classes
                        processEnvParamFields(requestObject, conversationId);
                    }
                }
            }
        }
        
        // Continue executing the original method with possibly modified parameter array
        return joinPoint.proceed(args);
    }
    
    /**
     * Process record type objects, create a new instance if needed from global variables
     * @param recordObject record type object
     * @param conversationId conversation ID
     * @return return a new instance if replacement is needed, otherwise return null
     */
    private Object processRecordObject(Object recordObject, String conversationId) {
        Class<?> clazz = recordObject.getClass();
        if (!clazz.isRecord()) {
            return null;
        }
        
        try {
            // Get all record components
            java.lang.reflect.RecordComponent[] components = clazz.getRecordComponents();
            
            // Create constructor argument array
            Object[] constructorArgs = new Object[components.length];
            boolean needNewInstance = false;
            
            // Collect current values and check if values need to be fetched from global variables
            for (int i = 0; i < components.length; i++) {
                java.lang.reflect.RecordComponent component = components[i];
                java.lang.reflect.Method accessor = component.getAccessor();
                Object fieldValue = accessor.invoke(recordObject);
                constructorArgs[i] = fieldValue;
                
                // Check for @EnvParam annotation
                EnvParam envParamAnnotation = component.getAnnotation(EnvParam.class);
                if (envParamAnnotation != null) {
                    String fieldName = component.getName();
                    
                    // Log the component value fetched from the record
                    log.info("Fetched component value from Record: {} = {}", fieldName, fieldValue);
                    
                    // If the component value is null, try to fetch from global variables
                    if (fieldValue == null) {
                        Object globalValue = chatEnvironmentMemory.get(conversationId, fieldName);
                        if (globalValue != null) {
                            // Convert value type
                            Object convertedValue = convertValue(globalValue, component.getType());
                            constructorArgs[i] = convertedValue;
                            needNewInstance = true;
                            log.info("Fetched value from global variables: {} = {}", fieldName, convertedValue);
                        }
                    }
                    
                    // If the component value is not null and setEnv is true, write to global variables
                    else if (fieldValue != null && envParamAnnotation.setEnv()) {
                        if (fieldValue instanceof String && StringUtils.isNotBlank(envParamAnnotation.pattern())) {
                            // If a regex is configured, it must match the regex condition to enter global variables
                            String pattern = envParamAnnotation.pattern();
                            if (!Pattern.matches(pattern, (String)fieldValue)) {
                                continue;
                            }
                        }
                        log.info("Writing to global variables: {} = {}", fieldName, fieldValue);
                        chatEnvironmentMemory.add(conversationId, Map.of(fieldName, fieldValue));
                    }
                }
            }
            
            // If a new instance is needed
            if (needNewInstance) {
                // Get constructor parameter types
                Class<?>[] paramTypes = new Class<?>[components.length];
                for (int i = 0; i < components.length; i++) {
                    paramTypes[i] = components[i].getType();
                }
                
                // Get constructor and create a new instance
                java.lang.reflect.Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes);
                return constructor.newInstance(constructorArgs);
            }
        } catch (Exception e) {
            log.error("Failed to process record type: {}", e.getMessage(), e);
        }
        
        return null;
    }
    
    private void processEnvParamFields(Object requestObject, String conversationId) {
        // Only process fields of regular classes, record types are handled by processRecordObject
        if (requestObject.getClass().isRecord()) {
            return;
        }
        
        // Process fields of regular classes
        Class<?> clazz = requestObject.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            // Get @EnvParam annotation on fields
            EnvParam envParamAnnotation = field.getAnnotation(EnvParam.class);
            if (envParamAnnotation != null) {
                try {
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    Object fieldValue = field.get(requestObject);
                    // If the value in request is null, write from global variables to request
                    if (fieldValue == null) {
                        Object globalValue = chatEnvironmentMemory.get(conversationId, fieldName);
                        log.info("Parameter auto-injection: Conversation ID: {}, Property {} auto-injected. Value: {}", conversationId, fieldName, globalValue);
                        field.set(requestObject, convertValue(globalValue, field.getType()));
                    }
                    // If the value in request is not null and setEnv is true, write back to global variables
                    else if (fieldValue != null && envParamAnnotation.setEnv()) {
                        if (fieldValue instanceof String && StringUtils.isNotBlank(envParamAnnotation.pattern())) {
                            // If a regex is configured, it must match the regex condition to enter global variables
                            String pattern = envParamAnnotation.pattern();
                            if (!Pattern.matches(pattern, (String)fieldValue)) {
                                continue;
                            }
                        }
                        log.info("Parameter auto-injection: Conversation ID: {}, Property {} auto-injected. Value: {}", conversationId, fieldName, fieldValue);
                        chatEnvironmentMemory.add(conversationId, Map.of(fieldName, fieldValue));
                    }
                } catch (IllegalAccessException e) {
                   logger.error("ToolEnvAspect.processEnvParamFields e:{}", e);
                }
            }
        }
    }

    public String getConversationId(Object[] args) {
        Object conversationId = ((ToolContext) args[1]).getContext().get(AgentsConstants.AgentsConversationEnvironmentKey.CONVERSATION_ID);
        return conversationId == null ? null : String.valueOf(conversationId);
    }

    /**
     * Type conversion: convert the value of global variables to field type
     */
    private Object convertValue(Object value, Class<?> targetType) {
        if (value == null || targetType.isInstance(value)) {
            return value;
        }
        if (targetType == String.class) {
            return String.valueOf(value);
        }
        if (targetType == Integer.class || targetType == int.class) {
            return Integer.parseInt(String.valueOf(value));
        }
        if (targetType == Boolean.class || targetType == boolean.class) {
            return Boolean.parseBoolean(String.valueOf(value));
        }
        throw new IllegalArgumentException("Unsupported type conversion for " + targetType.getName());
    }
}

